from data import Data
from random import randint, random,shuffle
import logging
import json


logging.basicConfig(level = logging.INFO,\
  format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
class GA():
  def __init__(self, d:Data):
      self.data = d
  
  #https://www.bilibili.com/read/cv14467767
  def run(self):
    # 根据当前序列计算时间
    def _computeTime(executer_list):
      # 第 i个任务，选择第k个机器的完成时间
      def calc_finish_time(i,k):
        nonlocal task_index, task_last_time, startTime
        return calc_start_time(i,k) + k[1]

      # 第i个任务，选择第k个机器的开始时间
      def calc_start_time(i,k):
        nonlocal task_index, task_last_time, startTime
        logging.debug(f"calc_start_time, {i}, {k}")
        return max(startTime[k[0]], task_last_time[i])

      m = self.data.m
      n = self.data.n
      startTime = [0 for i in range(m)]
      machine_bus = [[] for i in range(m)]
      task_index = [0 for i in range(n)]
      task_last_time = [0 for i in range(n)]
      max_time = 0
      for i in executer_list:
        # 找到最早完成的machine
        mac_index = -1
        mac_best_finish_time = 1e9
        mac_best_start_time = 1e9
        for j in self.data.a[i][task_index[i]]:
          now_finish_time = calc_finish_time(i, j)
          now_start_time = calc_start_time(i, j)
          if mac_best_finish_time > now_finish_time:
            mac_index = j[0]
            mac_best_finish_time = now_finish_time
            mac_best_start_time = now_start_time
        machine_bus[mac_index].append([i, task_index[i], mac_best_start_time,mac_best_finish_time])
        task_index[i] += 1
        task_last_time[i] = mac_best_finish_time
        startTime[mac_index] = mac_best_finish_time
        max_time = max(max_time, mac_best_finish_time)
      return max_time, machine_bus, executer_list
    # 交叉遗传
    def _cross(fa1, fa2, n):
      def _trans2Index(p1, n):
        ct = [0 for j in range(n)]
        s = []
        for i in p1:
            s.append(ct[i]*10000 + i)
            ct[i] = ct[i] + 1
        return s
      idx_p1 = _trans2Index(fa1,n)
      idx_p2 = _trans2Index(fa2,n)
      nt = len(idx_p1) # total number of tasks
      i = randint(1, nt)
      j = randint(0, nt-1)
      k = randint(0, nt)
      implant = idx_p1[j:min(j+i,nt)] + idx_p1[:j+i - min(j+i,nt)]
      set_implant = set(implant)
      def _remove_set(p, s):
        return [x%10000 for x in p if x not in s]
      child = _remove_set(idx_p2[:k], set_implant)  + [x % 10000 for x in implant] + _remove_set(idx_p2[k:], set_implant) 
      return child
    # 变异
    def _trans(executer_list):
      n = len(executer_list)
      i,j = randint(0,n-1),randint(0,n-1)
      more = [x for x in executer_list]
      more[i], more[j] = more[j], more[i]
      return more
    epoch = 100
    pc = 0.1
    pm = 0.1
    pop_with_time = [(_computeTime(g)[0],g) for g in self._init_group()]
    for i_epoch in range(epoch):
      shuffle(pop_with_time)
      pop_len = len(pop_with_time) 
      pop_half_len = len(pop_with_time) // 2
      for i in range(pop_half_len):
        if random() >pc:
          continue
        child_1 = _cross(pop_with_time[i][1], pop_with_time[pop_half_len+i][1], self.data.n)
        child_2 = _cross(pop_with_time[pop_half_len+i][1], pop_with_time[i][1], self.data.n)
        pop_with_time.append((_computeTime(child_1)[0],child_1))
        pop_with_time.append((_computeTime(child_2)[0],child_2))
      for i in range(pop_len):
        if random()>pm:
          continue
        trans_g = _trans(pop_with_time[i][1])
        pop_with_time.append((_computeTime(trans_g)[0], trans_g))
      pop_with_time.sort()
      pop_with_time = pop_with_time[:pop_len]
    logging.debug(json.dumps(pop_with_time))
    return _computeTime(pop_with_time[0][1])

  def _init_group(self,ps=1000):
    gene = [j for j in range(self.data.n) for t in range(len(self.data.a[j]))]
    population = []
    for i in range(ps):
      shuffle(gene)
      population.append([j for j in gene])
    return population

    